// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package modload

import (
	"context"
	"errors"
	"fmt"
	"go/build"
	"internal/goroot"
	"io/fs"
	"os"
	pathpkg "path"
	"path/filepath"
	"sort"
	"strings"

	"cmd/go/internal/cfg"
	"cmd/go/internal/fsys"
	"cmd/go/internal/modfetch"
	"cmd/go/internal/par"
	"cmd/go/internal/search"

	"golang.org/x/mod/module"
	"golang.org/x/mod/semver"
)

type ImportMissingError struct {
	Path     string
	Module   module.Version
	QueryErr error

	// isStd指出我们是否希望在标准中找到该包
	// 图书馆这通常适用于所有无点导入路径，但替换
	// 指令可以使我们将替换的路径视为也处于
	// 模块。
	isStd bool

	// 更换了更换所在模块的最高更换版本
	// 包含该包。仅当更换件未使用时，才设置“已更换”。
	replaced module.Version

	// newMissingVersion设置为模块的较新版本（如果存在）
	// 在构建列表中。设置后，我们无法自动升级。
	newMissingVersion string
}

func (e *ImportMissingError) Error() string {
	if e.Module.Path == "" {
		if e.isStd {
			return fmt.Sprintf("package %s is not in GOROOT (%s)", e.Path, filepath.Join(cfg.GOROOT, "src", e.Path))
		}
		if e.QueryErr != nil && e.QueryErr != ErrNoModRoot {
			return fmt.Sprintf("cannot find module providing package %s: %v", e.Path, e.QueryErr)
		}
		if cfg.BuildMod == "mod" || (cfg.BuildMod == "readonly" && allowMissingModuleImports) {
			return "cannot find module providing package " + e.Path
		}

		if e.replaced.Path != "" {
			suggestArg := e.replaced.Path
			if !module.IsZeroPseudoVersion(e.replaced.Version) {
				suggestArg = e.replaced.String()
			}
			return fmt.Sprintf("module %s provides package %s and is replaced but not required; to add it:\n\tgo get %s", e.replaced.Path, e.Path, suggestArg)
		}

		message := fmt.Sprintf("no required module provides package %s", e.Path)
		if e.QueryErr != nil {
			return fmt.Sprintf("%s: %v", message, e.QueryErr)
		}
		return fmt.Sprintf("%s; to add it:\n\tgo get %s", message, e.Path)
	}

	if e.newMissingVersion != "" {
		return fmt.Sprintf("package %s provided by %s at latest version %s but not at required version %s", e.Path, e.Module.Path, e.Module.Version, e.newMissingVersion)
	}

	return fmt.Sprintf("missing module for import: %s@%s provides %s", e.Module.Path, e.Module.Version, e.Path)
}

func (e *ImportMissingError) Unwrap() error {
	return e.QueryErr
}

func (e *ImportMissingError) ImportPath() string {
	return e.Path
}

// 含糊不清的导入错误表示在多个文件中找到的包的导入
// 生成列表中的模块，或在主模块及其供应商中都可以找到
// 目录
type AmbiguousImportError struct {
	importPath string
	Dirs       []string
	Modules    []module.Version // 空的或1:1的Dirs。
}

func (e *AmbiguousImportError) ImportPath() string {
	return e.importPath
}

func (e *AmbiguousImportError) Error() string {
	locType := "modules"
	if len(e.Modules) == 0 {
		locType = "directories"
	}

	var buf strings.Builder
	fmt.Fprintf(&buf, "ambiguous import: found package %s in multiple %s:", e.importPath, locType)

	for i, dir := range e.Dirs {
		buf.WriteString("\n\t")
		if i < len(e.Modules) {
			m := e.Modules[i]
			buf.WriteString(m.Path)
			if m.Version != "" {
				fmt.Fprintf(&buf, " %s", m.Version)
			}
			fmt.Fprintf(&buf, " (%s)", dir)
		} else {
			buf.WriteString(dir)
		}
	}

	return buf.String()
}

// DirectImportFromImplicitDependencyError直接指示包
// 由主模块中的包或测试导入，并且
// 在主模块的go.mod文件中不显式的依赖项。
type DirectImportFromImplicitDependencyError struct {
	ImporterPath string
	ImportedPath string
	Module       module.Version
}

func (e *DirectImportFromImplicitDependencyError) Error() string {
	return fmt.Sprintf("package %s imports %s from implicitly required module; to add missing requirements, run:\n\tgo get %s@%s", e.ImporterPath, e.ImportedPath, e.Module.Path, e.Module.Version)
}

func (e *DirectImportFromImplicitDependencyError) ImportPath() string {
	return e.ImporterPath
}

// 当我们需要检查时，将以只读模式报告ImportMissingSumError
// 如果一个模块包含一个包，但我们没有它的.zip文件的总和。
// 我们可能需要多个模块的总和来验证包的唯一性。
// None
// TODO（#43653）：将此类型的多个错误合并为一个错误
// 这就意味着对转换导入的根包使用“go-get”命令
// 来自缺少和的模块的包。load.CheckPackageErrors将被删除
// 这是合并错误的好地方，但我们需要附加导入
// 堆叠在这里。
type ImportMissingSumError struct {
	importPath                string
	found                     bool
	mods                      []module.Version
	importer, importerVersion string // 可选，但用于其他上下文
	importerIsTest            bool
}

func (e *ImportMissingSumError) Error() string {
	var importParen string
	if e.importer != "" {
		importParen = fmt.Sprintf(" (imported by %s)", e.importer)
	}
	var message string
	if e.found {
		message = fmt.Sprintf("missing go.sum entry needed to verify package %s%s is provided by exactly one module", e.importPath, importParen)
	} else {
		message = fmt.Sprintf("missing go.sum entry for module providing package %s%s", e.importPath, importParen)
	}
	var hint string
	if e.importer == "" {
		// 导入程序包未知，或缺少的程序包已在上命名
		// 命令行。建议“go mod download”（go mod download）用于可能
		// 提供包，因为这不应该改变go.mod。
		if len(e.mods) > 0 {
			args := make([]string, len(e.mods))
			for i, mod := range e.mods {
				args[i] = mod.Path
			}
			hint = fmt.Sprintf("; to add:\n\tgo mod download %s", strings.Join(args, " "))
		}
	} else {
		// 导入包是已知的（常见情况）。建议“上厕所”
		// 导入包的当前版本。
		tFlag := ""
		if e.importerIsTest {
			tFlag = " -t"
		}
		version := ""
		if e.importerVersion != "" {
			version = "@" + e.importerVersion
		}
		hint = fmt.Sprintf("; to add:\n\tgo get%s %s%s", tFlag, e.importer, version)
	}
	return message + hint
}

func (e *ImportMissingSumError) ImportPath() string {
	return e.importPath
}

type invalidImportError struct {
	importPath string
	err        error
}

func (e *invalidImportError) ImportPath() string {
	return e.importPath
}

func (e *invalidImportError) Error() string {
	return e.err.Error()
}

func (e *invalidImportError) Unwrap() error {
	return e.err
}

// importFromModules在的依赖关系图中查找模块和目录
// 包含具有给定导入路径的包的rs。如果mg为零，
// importFromModules尝试仅使用主模块定位模块
// 和rs的根，然后再加载整个图形。
// None
// 答案必须是唯一的：importFromModules在出现多个错误时返回错误
// 观察到模块提供相同的包。
// None
// 对于中的包，importFromModules可以返回具有空m.Path的模块
// 标准库。
// None
// importFromModules可以为假包返回空目录字符串
// 比如“C”和“不安全”。
// None
// 如果从需求中选择的任何模块中不存在包
// 图中，importFromModules返回一个*ImportMissingError。
func importFromModules(ctx context.Context, path string, rs *Requirements, mg *ModuleGraph) (m module.Version, dir string, err error) {
	if strings.Contains(path, "@") {
		return module.Version{}, "", fmt.Errorf("import path should not have @version")
	}
	if build.IsLocalImport(path) {
		return module.Version{}, "", fmt.Errorf("relative import not supported")
	}
	if path == "C" {
		// 没有用于导入“C”的目录。
		return module.Version{}, "", nil
	}
	// 在进一步查找之前，请检查路径是否有效。
	if err := module.CheckImportPath(path); err != nil {
		return module.Version{}, "", &invalidImportError{importPath: path, err: err}
	}

	// 软件包在标准库中吗？
	pathIsStd := search.IsStandardImportPath(path)
	if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
		if targetInGorootSrc {
			if dir, ok, err := dirInModule(path, targetPrefix, ModRoot(), true); err != nil {
				return module.Version{}, dir, err
			} else if ok {
				return Target, dir, nil
			}
		}
		dir := filepath.Join(cfg.GOROOT, "src", path)
		return module.Version{}, dir, nil
	}

	// -mod=供应商是特殊的。
	// 所有内容都必须位于主模块或主模块的供应商目录中。
	if cfg.BuildMod == "vendor" {
		mainDir, mainOK, mainErr := dirInModule(path, targetPrefix, ModRoot(), true)
		vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(ModRoot(), "vendor"), false)
		if mainOK && vendorOK {
			return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: []string{mainDir, vendorDir}}
		}
		// 如果有主目录，则更愿意返回主目录，
		// 请注意，我们没有检查包是否存在。
		// 我们把它留着装。
		if !vendorOK && mainDir != "" {
			return Target, mainDir, nil
		}
		if mainErr != nil {
			return module.Version{}, "", mainErr
		}
		readVendorList()
		return vendorPkgModule[path], vendorDir, nil
	}

	// 检查生成列表中的每个模块。
	var dirs []string
	var mods []module.Version

	// 迭代路径的可能模块，而不是所有选定模块。
	// 在选定的模块上进行迭代将缩短总的加载时间
	// O（M×P）表示提供P个导入包的M模块，而迭代
	// 过路径前缀仅为O（P×k），最大路径深度为k。对于
	// 大型项目M和P可能都非常大（请注意，M≤ P） ，但是k
	// 将倾向于保持较小（如果不是因为文件系统以外的原因
	// 路径限制）。
	// None
	// 我们执行这个迭代一次或两次。如果mg最初为零，
	// 然后，我们首先尝试仅使用主模块和
	// 它的根本要求。如果未识别包装，或如果mg
	// 已经是非nil，那么我们尝试使用完整的
	// 以毫克为单位的要求。
	for {
		var sumErrMods []module.Version
		for prefix := path; prefix != "."; prefix = pathpkg.Dir(prefix) {
			var (
				v  string
				ok bool
			)
			if mg == nil {
				v, ok = rs.rootSelected(prefix)
			} else {
				v, ok = mg.Selected(prefix), true
			}
			if !ok || v == "none" {
				continue
			}
			m := module.Version{Path: prefix, Version: v}

			needSum := true
			root, isLocal, err := fetch(ctx, m, needSum)
			if err != nil {
				if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
					// 我们缺少在生成列表中获取模块所需的总和。
					// 我们无法验证包是否唯一，并且可能找不到
					// 根本不需要这个包裹。继续检查其他模块以确定哪一个
					// 要报告的错误。如果我们需要查看，可能会丢失多个总和
					// 多个嵌套模块来解析导入；我们会全部报告的。
					sumErrMods = append(sumErrMods, m)
					continue
				}
				// 报告获取错误。
				// 请注意，我们不确定该模块是否必要，
				// 但它当然可以提供一揽子计划，即使我们
				// 继续循环并在其他模块中找到包，
				// 我们需要查看此模块以确保导入是正确的
				// 不含糊。
				return module.Version{}, "", err
			}
			if dir, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
				return module.Version{}, "", err
			} else if ok {
				mods = append(mods, m)
				dirs = append(dirs, dir)
			}
		}

		if len(mods) > 1 {
			// 我们生成从最长到最短候选目录的目录列表
			// 模块路径，但模棱两可的导入错误应该从
			// 从最短到最长。现在把它们倒过来。
			for i := 0; i < len(mods)/2; i++ {
				j := len(mods) - 1 - i
				mods[i], mods[j] = mods[j], mods[i]
				dirs[i], dirs[j] = dirs[j], dirs[i]
			}
			return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods}
		}

		if len(sumErrMods) > 0 {
			for i := 0; i < len(sumErrMods)/2; i++ {
				j := len(sumErrMods) - 1 - i
				sumErrMods[i], sumErrMods[j] = sumErrMods[j], sumErrMods[i]
			}
			return module.Version{}, "", &ImportMissingSumError{
				importPath: path,
				mods:       sumErrMods,
				found:      len(mods) > 0,
			}
		}

		if len(mods) == 1 {
			return mods[0], dirs[0], nil
		}

		if mg != nil {
			// 我们检查了完整的模块图，仍然没有找到
			// 要求的包裹。
			var queryErr error
			if !HasModRoot() {
				queryErr = ErrNoModRoot
			}
			return module.Version{}, "", &ImportMissingError{Path: path, QueryErr: queryErr, isStd: pathIsStd}
		}

		// 到目前为止，我们已经检查了根依赖项。
		// 加载完整的模块图，然后重试。
		mg, err = rs.Graph(ctx)
		if err != nil {
			// 我们可能缺少来自的一个或多个可传递（隐式）依赖项
			// 模块图，因此我们不能在此返回ImportMissingError-1
			// 缺少的模块中的一个可能实际上包含有问题的包，
			// 在这种情况下，我们不应该在新的依赖项中寻找它。
			return module.Version{}, "", err
		}
	}
}

// queryImport尝试查找可添加到当前应用程序的模块
// 生成列表以提供具有给定导入路径的包。
// None
// 与QueryPattern不同，queryImport更喜欢添加替换版本的
// 模块*在*检查要添加的版本的代理之前。
func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) {
	// 为了避免虚假的远程抓取，请尝试对每个文件进行最新的替换
	// 模块（golang.org/issue/26241）。
	if index != nil {
		var mods []module.Version
		for mp, mv := range index.highestReplaced {
			if !maybeInModule(path, mp) {
				continue
			}
			if mv == "" {
				// 唯一的替换是一个未指定版本的通配符，因此
				// 合成一个伪版本，其中包含适当的主版本和
				// 低于任何实际时间戳的时间戳。这样，如果主模块是
				// 从其他模块中使用，用户将能够升级
				// 对他们选择的任何真实版本的要求。
				if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 {
					mv = module.ZeroPseudoVersion(pathMajor[1:])
				} else {
					mv = module.ZeroPseudoVersion("v0")
				}
			}
			mg, err := rs.Graph(ctx)
			if err != nil {
				return module.Version{}, err
			}
			if cmpVersion(mg.Selected(mp), mv) >= 0 {
				// 我们无法通过添加来解决导入问题mp@mv对于模块图，
				// 因为所选版本的mp已至少为mv。
				continue
			}
			mods = append(mods, module.Version{Path: mp, Version: mv})
		}

		// mods中的每个模块路径都是导入路径的前缀。
		// 与QueryPattern中一样，首选满足导入的最长前缀。
		sort.Slice(mods, func(i, j int) bool {
			return len(mods[i].Path) > len(mods[j].Path)
		})
		for _, m := range mods {
			needSum := true
			root, isLocal, err := fetch(ctx, m, needSum)
			if err != nil {
				if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
					return module.Version{}, &ImportMissingSumError{importPath: path}
				}
				return module.Version{}, err
			}
			if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
				return m, err
			} else if ok {
				if cfg.BuildMod == "readonly" {
					return module.Version{}, &ImportMissingError{Path: path, replaced: m}
				}
				return m, nil
			}
		}
		if len(mods) > 0 && module.CheckPath(path) != nil {
			// 包路径无效，无法远程获取，
			// 所以它只能存在于被替换的模块中，
			// 从上面的循环我们知道它不是。
			return module.Version{}, &PackageNotInModuleError{
				Mod:         mods[0],
				Query:       "latest",
				Pattern:     path,
				Replacement: Replacement(mods[0]),
			}
		}
	}

	if search.IsStandardImportPath(path) {
		// 此包不在标准库中，也不在任何模块中
		// 在生成列表中，并且不在用户拥有的任何其他模块中
		// 通过“替换”指令填充。
		// 此外，导入路径是为标准库保留的，因此
		// QueryPattern可能找不到包含此包的模块。
		// None
		// 不要尝试QueryPattern，而是立即报告ImportMissingError。
		return module.Version{}, &ImportMissingError{Path: path, isStd: true}
	}

	if cfg.BuildMod == "readonly" && !allowMissingModuleImports {
		// 在只读模式下，我们无法编写go.mod，因此不应该尝试查找
		// 模块。如果已明确启用只读模式，请将其包括在
		// 错误消息。
		var queryErr error
		if cfg.BuildModExplicit {
			queryErr = fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
		} else if cfg.BuildModReason != "" {
			queryErr = fmt.Errorf("import lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
		}
		return module.Version{}, &ImportMissingError{Path: path, QueryErr: queryErr}
	}

	// 查找包含包的模块，以添加到生成列表中。
	// 目标是确定模块，将其下载到dir，
	// 返回m，dir，impportmissingeror。
	fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path)

	mg, err := rs.Graph(ctx)
	if err != nil {
		return module.Version{}, err
	}

	candidates, err := QueryPackages(ctx, path, "latest", mg.Selected, CheckAllowed)
	if err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			// 返回“找不到提供包[…]的模块”，而不是任何内容
			// 生成低级错误查询模式。
			return module.Version{}, &ImportMissingError{Path: path, QueryErr: err}
		} else {
			return module.Version{}, err
		}
	}

	candidate0MissingVersion := ""
	for i, c := range candidates {
		if v := mg.Selected(c.Mod.Path); semver.Compare(v, c.Mod.Version) > 0 {
			// QueryPattern建议我们添加模块c.Mod来提供包，
			// 但我们已经依赖于该模块的更新版本（以及
			// 版本没有该软件包）。
			// None
			// 当包以“@最新”的速度出现时，通常会发生这种情况
			// 模块的版本（如v1.0.0），但我们有一个更新的版本
			// 构建列表中相同模块的版本（例如v1.0.1-beta版），以及
			// 包裹不在那里。
			if i == 0 {
				candidate0MissingVersion = v
			}
			continue
		}
		return c.Mod, nil
	}
	return module.Version{}, &ImportMissingError{
		Path:              path,
		Module:            candidates[0].Mod,
		newMissingVersion: candidate0MissingVersion,
	}
}

// maybeInModule报告是否在语法上，
// 可以提供具有给定导入路径的包
// 由具有给定模块路径（mpath）的模块执行。
func maybeInModule(path, mpath string) bool {
	return mpath == path ||
		len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath
}

var (
	haveGoModCache   par.Cache // 迪尔→ 布尔
	haveGoFilesCache par.Cache // 迪尔→ goFilesEntry
)

type goFilesEntry struct {
	haveGoFiles bool
	err         error
}

// dirInModule定位将保存由给定路径命名的包的目录，
// 如果它位于具有模块路径mpath和根mdir的模块中。
// 如果路径在语法上不在mpath内，
// 或者如果mdir是本地文件树（isLocal==true）和目录
// 该路径位于子模块中（由mdir下面的go.mod覆盖），
// dirInModule返回“”，false，nil。
// None
// 否则，dirInModule返回目录的名称，其中
// Go源文件应与布尔值一起指示
// 该目录中是否确实存在Go源文件。
// 非nil错误表示目录和/或
// 无法确定源文件，例如，由于权限错误。
func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool, err error) {
	// 确定在何处需要包。
	if path == mpath {
		dir = mdir
	} else if mpath == "" { // 供应商目录
		dir = filepath.Join(mdir, path)
	} else if len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath {
		dir = filepath.Join(mdir, path[len(mpath)+1:])
	} else {
		return "", false, nil
	}

	// 检查是否有其他模块挡道。
	// 此检查在模块缓存内是不必要的
	// 重要的是要在供应商目录中跳过，
	// 其中所有模块树都已覆盖。
	// 所以我们只检查本地模块树
	// （主模块和replace指令指向的任何目录树）。
	if isLocal {
		for d := dir; d != mdir && len(d) > len(mdir); {
			haveGoMod := haveGoModCache.Do(d, func() interface{} {
				fi, err := fsys.Stat(filepath.Join(d, "go.mod"))
				return err == nil && !fi.IsDir()
			}).(bool)

			if haveGoMod {
				return "", false, nil
			}
			parent := filepath.Dir(d)
			if parent == d {
				// 打破循环，否则我们会循环
				// 如果d==”、mdir==”，则为永久。
				break
			}
			d = parent
		}
	}

	// 现在已承诺返回目录（而不是“”。

	// 目录中是否有Go源文件？
	// 我们不关心构建标签，甚至不关心“+buildignore”。
	// 我们只是在找一个合理的目录。
	res := haveGoFilesCache.Do(dir, func() interface{} {
		ok, err := fsys.IsDirWithGoFiles(dir)
		return goFilesEntry{haveGoFiles: ok, err: err}
	}).(goFilesEntry)

	return dir, res.haveGoFiles, res.err
}

// fetch下载给定模块（或其替换模块）
// 并返回其位置。
// None
// needSum表示是否可以在只读模式下下载模块
// 没有go.sum条目。对于获取的模块，它应该仅为false
// 推测性地（例如，对于不兼容的版本筛选）。总数
// 仍将正常验证。
// None
// isLocal返回值报告替换，
// 如果有，则是文件系统的本地。
func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, isLocal bool, err error) {
	if mod == Target {
		return ModRoot(), true, nil
	}
	if r := Replacement(mod); r.Path != "" {
		if r.Version == "" {
			dir = r.Path
			if !filepath.IsAbs(dir) {
				dir = filepath.Join(ModRoot(), dir)
			}
			// 确保替换目录实际存在：
			// dirInModule不报告缺失模块的错误，
			// 因此，如果我们现在不报告错误，那么以后的失败将被忽略
			// 非常神秘。
			if _, err := fsys.Stat(dir); err != nil {
				if os.IsNotExist(err) {
					// 从语义上讲，模块版本本身“存在”——我们只是不存在
					// 有它的源代码。删除与os.ErrNotExist的等价项，
					// 让我们的信息更简洁。
					err = fmt.Errorf("replacement directory %s does not exist", r.Path)
				} else {
					err = fmt.Errorf("replacement directory %s: %w", r.Path, err)
				}
				return dir, true, module.VersionError(mod, err)
			}
			return dir, true, nil
		}
		mod = r
	}

	if HasModRoot() && cfg.BuildMod == "readonly" && needSum && !modfetch.HaveSum(mod) {
		return "", false, module.VersionError(mod, &sumMissingError{})
	}

	dir, err = modfetch.Download(ctx, mod)
	return dir, false, err
}

type sumMissingError struct {
	suggestion string
}

func (e *sumMissingError) Error() string {
	return "missing go.sum entry" + e.suggestion
}
